/*
 * Copyright 2011 Katsunori Koyanagi
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package sweezy.swf.tools.flex
{
	import flash.utils.ByteArray;
	import flash.utils.Dictionary;
	import mx.collections.ArrayCollection;
	import mx.collections.ICollectionView;
	import sweezy.core.reflect.IProperty;
	import sweezy.core.reflect.Reflection;
	import sweezy.swf.abc.ABC;
	import sweezy.swf.encoder.ActionEncoder;
	import sweezy.swf.structure.ABCData;
	import sweezy.swf.structure.ActionRecords;

	public class SWFNode
	{

		private static var _objectTypePropertiesCache:Dictionary = new Dictionary();

		private static var _scalarTypePropertiesCache:Dictionary = new Dictionary();

		private static var _scalarTypes:Dictionary = new Dictionary();

		{
			_scalarTypes[int] = null;
			_scalarTypes[uint] = null;
			_scalarTypes[String] = null;
			_scalarTypes[Number] = null;
			_scalarTypes[ByteArray] = null;
			_scalarTypes[Boolean] = null;

			_scalarTypePropertiesCache[ActionRecords] = [];
			_scalarTypePropertiesCache[ABCData] = [];
			_scalarTypePropertiesCache[null] = [];
			_objectTypePropertiesCache[null] = [];
		}

		private var _arrayElement:Boolean;

		private var _children:ArrayCollection;

		private var _encodeData:Dictionary;

		private var _name:String;

		private var _root:SWFNode;

		private var _type:Class;

		private var _value:*;

		public function SWFNode(value:*, name:String, type:Class, arrayElement:Boolean, root:SWFNode)
		{
			_value = value;
			_name = name;
			_type = type;
			_arrayElement = arrayElement;
			_root = root === null ? this : root;
			_encodeData = root === null ? new Dictionary() : root._encodeData;
		}

		public function get children():ICollectionView
		{
			if (_children === null)
			{
				_children = new ArrayCollection(createChildren());
			}

			return _children;
		}

		public function encode():void
		{
			for (var object:Object in _encodeData)
			{
				if (object is ABCData)
				{
					var abcData:ABCData = object as ABCData;
					var abc:ABC = _encodeData[object] as ABC;
					abcData.data = abc.encode();
				}
			}
		}

		public function get label():String
		{
			var result:String = "";
			if (_arrayElement)
			{
				result += "[";
				result += _name;
				result += "]";
			}
			else
			{
				result += _name;
			}
			result += " : ";

			if (_type !== null)
			{
				result += Reflection.forClass(_type).shortName;
			}
			if (_value === null)
			{
				result += "(null)";
			}

			return result;
		}

		public function get scalarProperties():Array
		{
			if (_type === Array)
			{
				return createArrayScalarProperties();
			}

			if (_type in _scalarTypePropertiesCache)
			{
				return _scalarTypePropertiesCache[_type];
			}

			var properties:Array = _scalarTypePropertiesCache[_type] = [];
			for each (var reflectionProperty:IProperty in Reflection.forClass(_type).getProperties(propertyFilter))
			{
				if (reflectionProperty.type.rawClass in _scalarTypes)
				{
					properties[properties.length] = new Property(reflectionProperty, null, null);
				}
			}

			properties.sortOn("name");
			return properties;
		}

		public function get value():*
		{
			return _value;
		}

		private function createABCChildren():Array
		{
			if (_value === null)
			{
				return [];
			}

			var abc:ABC = new ABC();
			abc.decode((_value as ABCData).data);
			_encodeData[_value] = abc;

			return [new SWFNode(abc, "ABC", ABC, false, _root)];
		}

		private function createArrayChildren():Array
		{
			var result:Array = [];
			var index:uint;

			for each (var child:* in _value)
			{
				var childType:Class = child === null ? null : Reflection.forInstance(child).rawClass;
				if (childType in _scalarTypes)
				{
					index++;
					continue;
				}

				result[result.length] = new SWFNode(child, String(index++), childType, true, _root);
			}

			return result;
		}

		private function createArrayScalarProperties():Array
		{
			if (_value === null)
			{
				return [];
			}

			var properties:Array = [];
			for (var i:uint = 0, len:uint = _value.length; i < len; i++)
			{
				var value:* = _value[i];
				if (value === null)
				{
					continue;
				}

				var type:Class = Reflection.forInstance(value).rawClass;
				if (type in _scalarTypes)
				{
					properties[properties.length] = new Property(null, String(i), type);
				}
			}

			return properties;
		}

		private function createChildren():Array
		{
			if (_value === null)
			{
				return [];
			}

			if (_type === Array)
			{
				return createArrayChildren();
			}
			if (_type === ABCData)
			{
				return createABCChildren();
			}

			var properties:Array;
			var result:Array = [];

			if (_type in _objectTypePropertiesCache)
			{
				properties = _objectTypePropertiesCache[_type];
			}
			else
			{
				properties = _objectTypePropertiesCache[_type] = [];

				for each (var reflectionProperty:IProperty in Reflection.forClass(_type).getProperties(propertyFilter))
				{
					if (reflectionProperty.type.rawClass in _scalarTypes)
					{
						continue;
					}

					properties[properties.length] = new Property(reflectionProperty, null, null);
				}

				properties.sortOn("name");
			}

			for each (var property:Property in properties)
			{
				result[result.length] = new SWFNode(value[property.name], property.name, property.type, false, _root);
			}

			return result;
		}

		private function propertyFilter(property:IProperty, index:uint, array:Array):Boolean
		{
			if (!property.isVariable)
			{
				return false;
			}
			if (property.isConst)
			{
				return false;
			}
			if (property.isStatic)
			{
				return false;
			}

			return true;
		}
	}
}
